var _ctCp437High = 'ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ '; // Windows-1252 differs from Latin-1 only in 128-159; the five unassigned bytes keep their Latin-1 control code. var _ctCp1252Mid = '€\u0081‚ƒ„…†‡ˆ‰Š‹Œ\u008dŽ\u008f\u0090‘’“”•–—˜™š›œ\u009džŸ'; function _ctOpt(id, fallback) { var el = document.getElementById(id); if (!el) return fallback; return el.type === 'checkbox' ? el.checked : el.value; } // Code points are stored as UTF-8 bytes; 😀 style surrogate pairs are joined first. function _ctPushCodePoint(out, cp) { if (out.highSurrogate) { var hi = out.highSurrogate; out.highSurrogate = 0; if (cp >= 0xDC00 && cp <= 0xDFFF) cp = 0x10000 + ((hi - 0xD800) << 10) + (cp - 0xDC00); else _ctPushCodePoint(out, 0xFFFD); } if (cp >= 0xD800 && cp <= 0xDBFF) { out.highSurrogate = cp; return; } if (cp > 0x10FFFF || (cp >= 0xDC00 && cp <= 0xDFFF)) cp = 0xFFFD; new TextEncoder().encode(String.fromCodePoint(cp)).forEach(function(b){ out.bytes.push(b); }); out.codePoints = true; } function _ctPushHexRun(out, run) { if (run.length === 1) run = '0' + run; if (run.length % 2) { out.oddDigit = true; run = run.slice(0, -1); } var pairs = run.match(/../g) || []; for (var i = 0; i !== pairs.length; i++) out.bytes.push(parseInt(pairs[i], 16)); } // Plain hex: 48 65 6C, 48656C, 0x48,0x65, \x48\x65, %48%65, 48h, $48, U+0048, H function _ctParsePlain(s, out, asCodePoints) { // A run wrapped onto several lines is one run, even when a line ends mid-byte. if (!asCodePoints && /\n/.test(s.trim()) && /^[0-9a-f\r\n]+$/i.test(s.trim())) s = s.replace(/\s+/g, ''); var re = /(U\+|\\u\{?|&#x)([0-9a-f]{1,6})[};]?|(?:0x|\\x|%|\$|#)?([0-9a-z]+)/gi, m; while ((m = re.exec(s))) { if (m[2]) { _ctPushCodePoint(out, parseInt(m[2], 16)); continue; } var t = m[3]; if (/^[0-9a-f]+h$/i.test(t) && t.length !== 1) t = t.slice(0, -1); if (!/^[0-9a-f]+$/i.test(t)) { out.skipped.push(m[0]); continue; } out.tokens++; if (!/^\d+$/.test(t) || m[0] !== t) out.allDigits = false; else if (t.length === 3 && +t <= 255) out.threeDigit = true; if (asCodePoints) _ctPushCodePoint(out, parseInt(t, 16)); else _ctPushHexRun(out, t); } } // Escaped text keeps ordinary characters: Hello\x20World, caf%C3%A9, b'\x89PNG\r\n' function _ctParseEscaped(s, out) { var q = s.trim().match(/^b?(['\x22])([\s\S]*)\1$/); if (q) s = q[2]; var simple = { n: 10, r: 13, t: 9, '0': 0, a: 7, b: 8, f: 12, v: 11, e: 27 }; var enc = new TextEncoder(); for (var i = 0; i !== s.length; i++) { var c = s.charAt(i), rest = s.slice(i + 1, i + 12), m; if (c === '\\' && (m = rest.match(/^x([0-9a-f]{2})/i))) { out.bytes.push(parseInt(m[1], 16)); i += m[0].length; continue; } if (c === '\\' && (m = rest.match(/^u\{([0-9a-f]{1,6})\}|^u([0-9a-f]{4})/i))) { _ctPushCodePoint(out, parseInt(m[1] || m[2], 16)); i += m[0].length; continue; } if (c === '\\' && simple.hasOwnProperty(rest.charAt(0))) { out.bytes.push(simple[rest.charAt(0)]); i++; continue; } if (c === '\\' && rest.length) { c = rest.charAt(0); i++; } else if (c === '%' && (m = rest.match(/^[0-9a-f]{2}/i))) { out.bytes.push(parseInt(m[0], 16)); i += 2; continue; } var cp = s.codePointAt(i); if (cp > 0xFFFF) i++; enc.encode(String.fromCodePoint(cp)).forEach(function(b){ out.bytes.push(b); }); } } var _ctOffsetLine = /^\s*(?:0x)?([0-9a-f]{4,16})(:?)(\s+|$)/i; function _ctLooksLikeDump(lines) { if (!lines.length) return false; if (lines.some(function(l){ return /^\s*(?:0x)?[0-9a-f]{4,16}:\s/i.test(l) || (_ctOffsetLine.test(l) && /\|.*\|\s*$/.test(l)); })) return true; if (lines.length === 1) return /^\s*0{4,16}\s{2,}[0-9a-f]/i.test(lines[0]); var offs = []; for (var i = 0; i !== lines.length; i++) { var m = lines[i].match(_ctOffsetLine); if (!m) return false; offs.push(parseInt(m[1], 16)); } var step = offs[1] - offs[0]; if (step <= 0 || step > 64) return false; if (lines.length === 2 && offs[0] !== 0) return false; for (var j = 2; j !== offs.length; j++) { if (offs[j] - offs[j - 1] !== step && !(j === offs.length - 1 && offs[j] > offs[j - 1] && offs[j] - offs[j - 1] <= step)) return false; } return true; } function _ctPrintableScore(bytes) { var n = 0; bytes.forEach(function(b){ if ((b >= 32 && b !== 127 && b < 128) || b === 10 || b === 13 || b === 9) n++; }); return n; } // xxd, hexdump -C, hexdump/od -x, Wireshark: leave out the offset and the text column on the right. function _ctParseDump(lines, out) { var rows = [], perLine = 0, gutterCol = -1, wordsNoColon = false, sevenDigit = false; lines.forEach(function(l){ var m = l.match(_ctOffsetLine); rows.push({ off: m ? parseInt(m[1], 16) : -1, digits: m ? m[1].length : 0, colon: m ? m[2] === ':' : false, start: m ? m[0].length : 0, line: l.replace(/\s+$/, '') }); }); if (rows.length > 1 && rows[0].off >= 0 && rows[1].off > rows[0].off) perLine = rows[1].off - rows[0].off; rows.forEach(function(r, idx){ var data = r.line.slice(r.start); var bar = data.indexOf('|'); if (bar !== -1) data = data.slice(0, bar); else if (gutterCol !== -1 && r.line.length > gutterCol) data = r.line.slice(r.start, gutterCol); else if (!perLine) { // No offsets to measure by: a right-hand column about as long as the bytes before it is the text column. var split = data.match(/^(.*\S)\s{2,}(\S.*)$/); if (split) { var headBytes = split[1].replace(/\s/g, '').length / 2; if (split[2].length <= headBytes && split[2].length >= headBytes - 2) data = split[1]; } } var re = /\S+/g, m, got = [], groupLen = 0; while ((m = re.exec(data))) { if (perLine && got.length >= perLine) break; if (!/^([0-9a-f]{2}){1,8}$/i.test(m[0])) break; if (!groupLen) groupLen = m[0].length; else if (m[0].length > groupLen) break; for (var k = 0; k !== m[0].length; k += 2) got.push(parseInt(m[0].substr(k, 2), 16)); if (perLine && got.length === perLine && gutterCol === -1 && bar === -1) gutterCol = r.start + re.lastIndex; } if (groupLen === 4 && !r.colon) { wordsNoColon = true; if (r.digits === 7) sevenDigit = true; } out.bytes.push.apply(out.bytes, got); }); // hexdump and od -x (seven-digit offsets) print 16-bit words in little-endian order: 6548 is 'He'. // Elsewhere the order is only swapped when that reads as more text. if (wordsNoColon && out.bytes.length % 2 === 0) { var swapped = []; for (var i = 0; i !== out.bytes.length; i += 2) swapped.push(out.bytes[i + 1], out.bytes[i]); if (sevenDigit || _ctPrintableScore(swapped) > _ctPrintableScore(out.bytes)) { out.bytes = swapped; out.swapped = true; } } // hexdump ends with a line holding only the total length, which drops the padding byte of an odd length. var last = rows[rows.length - 1]; if (rows.length > 1 && last.off > 0 && !last.line.slice(last.start).trim() && last.off !== out.bytes.length && out.bytes.length - last.off === 1) out.bytes.length = last.off; } function _ctParseHexInput(input, format) { var out = { bytes: [], skipped: [], tokens: 0, allDigits: true, threeDigit: false, oddDigit: false, codePoints: false, highSurrogate: 0, format: format }; var s = input.replace(/^/, ''); if (!s.trim()) return out; var lines = s.split(/\r\n|\r|\n/).filter(function(l){ return l.trim(); }); if (format === 'auto') { if (_ctLooksLikeDump(lines)) format = 'dump'; else { // Escapes mixed with ordinary words are escaped text; escapes on their own are plain hex. var hasEscape = /\\x[0-9a-f]{2}|%[0-9a-f]{2}|\\u[0-9a-f]{4}/i.test(s); var residue = s.replace(/\\x[0-9a-f]{2}|%[0-9a-f]{2}|\\u\{?[0-9a-f]{1,6}\}?|U\+[0-9a-f]{1,6}|&#x[0-9a-f]+;?|0x|[0-9a-f\s,;:|\-{}\[\]()'\x22`.]/gi, ''); format = hasEscape && residue.length ? 'escaped' : 'plain'; } } out.format = format; if (format === 'dump') _ctParseDump(lines, out); else if (format === 'escaped') _ctParseEscaped(s, out); else _ctParsePlain(s, out, format === 'codepoints'); if (out.highSurrogate) _ctPushCodePoint(out, 0xFFFD); return out; } function _ctMapBytes(bytes, map) { var parts = []; for (var i = 0; i !== bytes.length; i++) parts.push(map(bytes[i])); return parts.join(''); } function _ctDetectCharset(bytes, parsed) { if (parsed.codePoints) return 'utf8'; var b = bytes; if (b[0] === 0xEF && b[1] === 0xBB && b[2] === 0xBF) return 'utf8'; if (b[0] === 0xFE && b[1] === 0xFF) return 'utf16be'; if (b[0] === 0xFF && b[1] === 0xFE) return 'utf16le'; if (b.length >= 4 && b.length % 2 === 0) { var zEven = 0, zOdd = 0, pairs = b.length / 2; for (var i = 0; i !== b.length; i += 2) { if (!b[i]) zEven++; if (!b[i + 1]) zOdd++; } if (zEven >= pairs * 0.5 && zOdd * 4 <= pairs) return 'utf16be'; if (zOdd >= pairs * 0.5 && zEven * 4 <= pairs) return 'utf16le'; } try { new TextDecoder('utf-8', { fatal: true }).decode(new Uint8Array(b)); return 'utf8'; } catch (e) { return 'cp1252'; } } function _ctDecodeHexBytes(bytes, charset) { if (charset === 'cp437') return _ctMapBytes(bytes, function(b){ return b < 128 ? String.fromCharCode(b) : _ctCp437High.charAt(b - 128); }); if (charset === 'cp1252') return _ctMapBytes(bytes, function(b){ return b >= 128 && b < 160 ? _ctCp1252Mid.charAt(b - 128) : String.fromCharCode(b); }); if (charset === 'latin1') return _ctMapBytes(bytes, function(b){ return String.fromCharCode(b); }); var label = { utf16be: 'utf-16be', utf16le: 'utf-16le' }[charset] || 'utf-8'; // The decoder would quietly drop a BOM, which is fine; a leftover odd byte becomes a replacement character. return new TextDecoder(label).decode(new Uint8Array(bytes)); } function _ctShowControls(text) { return text.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, function(ch){ var c = ch.charCodeAt(0); return String.fromCharCode(c === 127 ? 0x2421 : 0x2400 + c); }); } function _ctShow(id, visible) { var el = document.getElementById(id); if (el) el.style.display = visible ? '' : 'none'; } function convert(input) { var status = document.getElementById('h2t-status'); var parsed = _ctParseHexInput(input || '', _ctOpt('h2t-format', 'auto')); var bytes = parsed.bytes; var charset = _ctOpt('h2t-charset', 'auto'); var used = charset === 'auto' ? _ctDetectCharset(bytes, parsed) : charset; var out = bytes.length ? _ctDecodeHexBytes(bytes, used) : ''; if (_ctOpt('h2t-controls', true)) out = _ctShowControls(out); var looksLikeText = !bytes.length && parsed.skipped.length && parsed.format !== 'dump'; // Hex that decodes to more hex, common in puzzles and double-encoded data. var again = out.length >= 4 && /^[\s,]*([0-9a-f]{2}[\s,]*)+$/i.test(out) && /[0-9a-f]{2}\s*[0-9a-f]{2}/i.test(out); var looksDecimal = parsed.format === 'plain' && parsed.tokens > 1 && parsed.allDigits && parsed.threeDigit; _ctShow('h2t-reverse', looksLikeText); _ctShow('h2t-ascii', looksDecimal); _ctShow('h2t-again', again); if (status) { var names = { utf8: 'UTF-8', utf16be: 'UTF-16 BE', utf16le: 'UTF-16 LE', cp1252: 'Windows-1252', latin1: 'Latin-1', cp437: 'DOS code page 437' }; var msg = ''; if (looksLikeText) msg = 'This looks like text, not hex.'; else if (bytes.length) { msg = 'Decoded ' + bytes.length + ' byte' + (bytes.length === 1 ? '' : 's'); if (parsed.format === 'dump') msg += ' from a hex dump (offsets and text column left out' + (parsed.swapped ? ', 16-bit words byte-swapped' : '') + ')'; if (parsed.format === 'escaped') msg += ' from escaped text'; msg += ' as ' + names[used] + '.'; if (used === 'utf8' && out.indexOf('�') !== -1) msg += ' Some bytes are not valid UTF-8 (shown as �); try another character set.'; if (looksDecimal) msg += ' These look like decimal codes rather than hex.'; else if (parsed.oddDigit) msg += ' Ignored a trailing digit: each byte needs two hex digits.'; if (parsed.skipped.length) { var shown = parsed.skipped.slice(0, 3).map(function(t){ return t.length > 12 ? t.slice(0, 12) + '...' : t; }).join(', '); msg += ' Skipped ' + parsed.skipped.length + ' value' + (parsed.skipped.length === 1 ? '' : 's') + ' that ' + (parsed.skipped.length === 1 ? 'is' : 'are') + ' not hex: ' + shown + (parsed.skipped.length > 3 ? ', ...' : '') + '.'; } } status.textContent = msg; } return out; } if (typeof document.addEventListener === 'function') document.addEventListener('click', function(e){ if (!e.target || e.target.id !== 'h2t-again') return; e.preventDefault(); var box1 = document.getElementById('box1'), box2 = document.getElementById('box2'); if (!box1 || !box2) return; box1.value = box2.value; box1.dispatchEvent(new Event('keyup')); }); var _loadedScripts = {}; function loadScriptPromise(url) { if (_loadedScripts[url]) return _loadedScripts[url]; _loadedScripts[url] = new Promise(function (resolve, reject) { var s = document.createElement('script'); s.src = url; s.onload = resolve; s.onerror = reject; document.head.appendChild(s); }); return _loadedScripts[url]; } function replaceAll(find, replace, str) { return str.replace(new RegExp(find, 'g'), replace); } function beautify(str) { var result = ''; var length = str.length; var i = 0; var braceCountLeft = 0; var braceCountRight = 0; var withinQuotes = false; while (i < length) { var c = str[i]; if (c == '"' && (i == 0 || c[i - 1] != '\\')) { // non-escaped quotes withinQuotes = !withinQuotes; } if (!withinQuotes && (c == '}' || c == '{' || c == ',')) { console.log('Start####' + result); // look back and remove carriage returns and whitespace that are already there var resultIndex = result.length - 1; while (resultIndex >= 0 && (result[resultIndex] == ' ' || result[resultIndex] == '\r' || result[resultIndex] == '\n' || result[resultIndex] == '\t')) { resultIndex = resultIndex - 1; result = result.substr(0, resultIndex + 1); console.log('char ' + result[resultIndex] + '-----' + result + 'zzz ' + result.length + ' ' + resultIndex); } if (c == '{') { braceCountLeft++; result += c + '\r' + GetTabs(braceCountLeft - braceCountRight); } else if (c == '}') { braceCountRight++; // precede with carriage return result += '\r' + GetTabs(braceCountLeft - braceCountRight) + c; } else if (c == ',') { result += c + '\r' + GetTabs(braceCountLeft - braceCountRight); } var nextChar = ''; // advance through whitespace and remove carriage returns that are already there while (i < length && (str[i + 1] == ' ' || str[i + 1] == '\r' || str[i + 1] == '\n' || str[i + 1] == '\t')) { i++; } } else { result += str[i]; } i++; } return result; } function GetTabs(count) { var result = ''; for (var i = 0; i < count; i++) { result += ' '; } return result; }